﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using System.Xml;

namespace MSBuild.DotNetNuke.Tasks
{
    /// <summary>
    /// Base class for all DotNetNuke component classes.
    /// </summary>
    public abstract class Component : Task
    {
        #region Properties
        /// <summary>
        /// DotNetNuke installer manifest file
        /// </summary>
        [Required]
        public string ManifestFile { get; set; }

        /// <summary>
        /// Relative path for the files
        /// </summary>
        public string BasePath { get; set; }

        /// <summary>
        /// Remove path where the file will be saved
        /// </summary>
        public string Path { get; set; }

        /// <summary>
        /// Gets or sets the list of files to be included in the component.
        /// </summary>
        /// <value>The files to include</value>
        [Required]
        public ITaskItem[] Files { get; set; }
        #endregion

        #region Members
        /// <summary>
        /// Type of the component. Must be specified on derived classes. 
        /// Allowed values are:
        /// Script, Assembly
        /// </summary>
        protected ComponentType _type;
        #endregion

        /// <summary>
        /// Executes the task.
        /// </summary>
        /// <returns>true is task completes succesfully, false if there is an error</returns>
        public override bool Execute()
        {
            if (!System.IO.File.Exists(ManifestFile))
            {
                Log.LogError("Manifest file not found: {0}", ManifestFile);
                return false;
            }

            try
            {
                string component = GetComponentNode();

                XmlDocument xmlComp = new XmlDocument();
                xmlComp.Load(new System.IO.StringReader(component));
                XmlNode xmlSourceScript = xmlComp.SelectSingleNode("component");

                if (xmlSourceScript == null)
                {
                    Log.LogWarning("No script was found.");
                    return true;
                }

                XmlDocument xmlTarget = new XmlDocument();
                xmlTarget.Load(ManifestFile);

                XmlNode xmlComponents = xmlTarget.SelectSingleNode("dotnetnuke/packages/package/components");
                if (xmlComponents == null)
                {
                    xmlComponents = xmlTarget.CreateElement("components");
                    xmlTarget.SelectSingleNode("dotnetnuke/packages/package").AppendChild(xmlComponents);
                }

                XmlNode xmlScript = xmlComponents.SelectSingleNode("component[@type=\"" + GetComponentType() + "\"]");
                if (xmlScript != null)
                {
                    xmlComponents.RemoveChild(xmlScript);
                }

                xmlComponents.AppendChild(xmlTarget.ImportNode(xmlSourceScript, true));

                xmlTarget.Save(ManifestFile);

                return true;
            }
            catch (Exception ex)
            {
                Log.LogErrorFromException(ex);
                return false;
            }
        }

        #region Private methods
        private string GetComponentNode()
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;

            StringBuilder sb = new StringBuilder();
            using (XmlWriter output = XmlWriter.Create(sb, settings))
            {
                output.WriteStartElement("component");
                output.WriteAttributeString("type", GetComponentType());

                output.WriteStartElement(GetContainerElement()); // <scripts>, <assemblies>, ...
                if (!string.IsNullOrEmpty(BasePath))
                {
                    output.WriteElementString("basePath", BasePath);
                }
                int count = 0;
                foreach (ITaskItem item in Files)
                {
                    WriteItemElement(output, item);
                    count++;
                }
                output.WriteEndElement(); // </scripts>, </assemblies>, ...

                output.WriteEndElement(); // </component>
                output.Flush();

                Log.LogMessage(MessageImportance.Low, sb.ToString());
                Log.LogMessage(MessageImportance.Normal, string.Format("{0} component: {1} {2} included.",
                    _type.ToString(), count, GetContainerElement()));
            }
            return sb.ToString();
        }
        #endregion

        #region Virtual and abstract methods
        protected virtual string GetComponentType()
        {
            return _type.ToString();
        }
        protected abstract string GetContainerElement();
        protected virtual string GetItemElement()
        {
            return _type.ToString().ToLower();
        }
        protected abstract void WriteItemElement(XmlWriter output, ITaskItem item);
        #endregion
    }
}
